package com.stox.ui.javafx.charting.widget;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javafx.event.EventHandler;
import javafx.geometry.Side;
import javafx.scene.canvas.Canvas;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

import com.stox.ui.javafx.charting.draw.shape.AbstractShape;
import com.stox.ui.javafx.charting.model.Grid;
import com.stox.ui.javafx.charting.model.plot.AbstractPlot;
import com.stox.ui.javafx.charting.view.ChartingView;
import com.stox.ui.javafx.charting.widget.axis.AbstractVerticalAxis;
import com.stox.ui.javafx.charting.widget.axis.BlankVerticalAxis;
import com.stox.ui.javafx.charting.widget.axis.ValueAxis;

@SuppressWarnings({"rawtypes", "unchecked"})
public abstract class Chart<T extends AbstractPlot> extends BorderPane{

	private T frontPlot;
	
	private final Grid grid = new Grid();
	
	private final ChartingView chartingView;
	
	private ResizableCanvas canvas;
	
	// TODO Use SimpleListProperty here
	private final List<AbstractShape> shapes = new ArrayList<AbstractShape>();
	
	public List<AbstractShape> getShapes() {
		return shapes;
	}
	
	private final PlotInfoPane plotInfoPane = new PlotInfoPane(this);
	
	private final List<T> plots = new ArrayList<>();
	
	private AbstractVerticalAxis leftVerticalAxis = new ValueAxis();
	
	private AbstractVerticalAxis rightVerticalAxis = new BlankVerticalAxis();
	
	public Chart(ChartingView chartingView) {
		this.chartingView = chartingView;
		getStyleClass().add("stox-chart");
	}
	
	@Autowired
	public void setApplicationContext(ApplicationContext applicationContext){
		canvas = (ResizableCanvas)applicationContext.getBean("resizableCanvas", this);
		
		StackPane pane = new StackPane();
		pane.setMinSize(100, 100);
		pane.getChildren().addAll(canvas, plotInfoPane);
		/**
		 * As transparent plotInfoPane covers entire canvas, no events are fired on canvas itself.
		 * Thus passing the events to canvas explicitly.
		 * There must be a better way to do this.
		 */
		plotInfoPane.addEventHandler(MouseEvent.ANY, new EventHandler<MouseEvent>(){
			@Override
			public void handle(MouseEvent event) {
				MouseEvent e = event.copyFor(canvas, Chart.this);
				canvas.fireEvent(e);
			}
		});
		setCenter(pane);
		canvas.widthProperty().bind(pane.widthProperty());
		canvas.heightProperty().bind(pane.heightProperty());
		
		ValueAxis valueAxis = applicationContext.getBean("valueAxis",ValueAxis.class);
		valueAxis.setChart(this);
		valueAxis.setSide(Side.LEFT);
		valueAxis.setUpdateGrid(true);
		leftVerticalAxis = valueAxis;
		
		rightVerticalAxis = applicationContext.getBean(BlankVerticalAxis.class);
		rightVerticalAxis.setChart(this);
		rightVerticalAxis.setSide(Side.RIGHT);
	}
	
	public void update() {
		if(null == frontPlot) return;
		frontPlot.update();
		
		for(T abstractPlot : getPlots()) {
			abstractPlot.update();
		}		
		
		for(AbstractShape shape : shapes){
			shape.update();
		}
	}
	
	public void paint() {
		if(null == frontPlot) return;
		
		getFrontPlot().paintBackground();
		
		grid.paint(canvas.getGraphicsContext2D());
		
		for(T abstractPlot : getPlots()) {
			abstractPlot.paint();
		}
		
		frontPlot.paint();
		
		for(AbstractShape shape : shapes){
			shape.paint();
		}

		getChartingView().getCursorType().getCursor().paint(this);
		
		if(null != leftVerticalAxis) leftVerticalAxis.paint();
		if(null != rightVerticalAxis) rightVerticalAxis.paint();

	}
	
	public Canvas getCanvas() {
		return canvas;
	}
	
	public List<T> getPlots() {
		return Collections.unmodifiableList(plots);
	}
	
	public void addPlot(int index, T plot) {
		plots.add(index, plot);
		plot.setChart(this);
		plotInfoPane.update();
	}
	
	public void addPlot(T plot) {
		if(!plots.contains(plot)){
			plots.add(plot);
			plot.setChart(this);
			plotInfoPane.update();
		}
	}

	public void removePlot(T plot) {
		plots.remove(plot);
		plot.setChart(null);
		plotInfoPane.update();
	}
	
	public ChartingView getChartingView() {
		return chartingView;
	}
	
	public Grid getGrid() {
		return grid;
	}

	public T getFrontPlot() {
		return frontPlot;
	}

	public void setFrontPlot(T frontPlot) {
		this.frontPlot = frontPlot;
		frontPlot.setChart(this);
		plotInfoPane.update();
	}

	public AbstractVerticalAxis getLeftVerticalAxis() {
		return leftVerticalAxis;
	}

	public void setLeftVerticalAxis(AbstractVerticalAxis leftVerticalAxis) {
		this.leftVerticalAxis = leftVerticalAxis;
		setLeft(leftVerticalAxis);
		leftVerticalAxis.setHeight(getHeight());
	}

	public AbstractVerticalAxis getRightVerticalAxis() {
		return rightVerticalAxis;
	}

	public void setRightVerticalAxis(AbstractVerticalAxis rightVerticalAxis) {
		this.rightVerticalAxis = rightVerticalAxis;
		setRight(rightVerticalAxis);
		rightVerticalAxis.setHeight(getHeight());
	}
	
	public void setGridVisible(boolean value){
		grid.setVisible(value);
	}
	
	public boolean isGridVisible(){
		return grid.isVisible();
	}
	
	public PlotInfoPane getPlotInfoPane() {
		return plotInfoPane;
	}
}
